# 表示课程时间的类
class ClassTime:
    # 表示课程的每周上课天数，1表示上课，0表示不课
    days = "1111111"
    # 课程开始时间的表示
    start = 0
    # 课程时长
    length = 0
    # 表示课程的每周上课周次，1表示上课，0表示不课
    weeks = "1111111111111"

scheme = {}
def CheckRequiredDistribution(curscheme,distributions):
    global scheme
    scheme = curscheme
    for dis in distributions:
        # 将由逗号分割的字符串变量dis[1]转换为int列表存入classlist
        classlist = [int(x) for x in dis[1].split(",")]
        res = True
        match dis[0]:
            case "SameStart":
                res = SameStart(classlist,True, 0)
            case "SameTime":
                res = SameTime(classlist,True, 0)
            case "DifferentTime":
                res = DifferentTime(classlist,True, 0)
            case "SameDays":
                res = SameDays(classlist,True, 0)
            case "SameWeeks":
                res = SameWeeks(classlist,True, 0)
            case "DifferentDays":
                res = DifferentDays(classlist,True, 0)
            case "DifferentWeeks":
                res = DifferentWeeks(classlist,True, 0)
            case "Overlap":
                res = Overlap(classlist,True, 0)
            case "NotOverlap":
                res = NotOverlap(classlist,True, 0)
            case _:
                res = True   # 未知的约束类型
        if not res:
            return True
    return True

def CheckPendingDistribution(curscheme,distributions):
    global scheme
    scheme = curscheme
    res = 0
    for dis in distributions:
        # 将由逗号分割的字符串变量dis[1]转换为int列表存入classlist
        classlist = [int(x) for x in dis[1].split(",")]
        penaty = dis[2]
        match dis[0]:
            case "SameStart":
                res += SameStart(classlist,False, penaty)
            case "SameTime":
                res += SameTime(classlist,False, penaty)
            case "DifferentTime":
                res += DifferentTime(classlist,False, penaty)
            case "SameDays":
                res += SameDays(classlist,False, penaty)
            case "SameWeeks":
                res += SameWeeks(classlist,False, penaty)
            case "DifferentDays":
                res += DifferentDays(classlist,False, penaty)
            case "DifferentWeeks":
                res += DifferentWeeks(classlist,False, penaty)
            case "Overlap":
                res += Overlap(classlist,False, penaty)
            case "NotOverlap":
                res += NotOverlap(classlist,False, penaty)
            case _:
                res += 0   # 未知的约束类型
    return res


# 获取课程时间的函数，接受课程ID列表并返回课程时间列表
def GetClassTime(classlist):
    timelist = []
    for classid in classlist:
        newtime = ClassTime()
        # 通过classid获取课程信息,填充ClassTime对象
        for task in scheme:
            if task[2]==classid:
                newtime.days = task[5][1][0]
                newtime.start = task[5][1][1]
                newtime.length = task[5][1][2]
                newtime.weeks = task[5][1][3]
                break
        timelist.append(newtime)
    return timelist


#给定的课程必须在同一时间段开始
def SameStart(classlist, isrequired, penalty):
    totalpenalty = 0
    #先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，计算每一对课程的起始时间是否相同
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if timelist[i].start != timelist[j].start:
                if isrequired:
                    return False   # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)   # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的起始时间相同，返回True



# 此约束中的所有课程必须在一天中与集合中最长的课程所在的相同时间内提供，即它们满足的时间必须与约束中最长的课程重叠。
# 先找到时间最长的课程，然后判断其他课程的结束时间是否和最长的课程相同，如果相同，则返回True，否则返回False。
def SameTime(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 找到时间最长的课程
    maxlen = 0
    maxindex = 0
    for i in range(len(timelist)):
        if timelist[i].length > maxlen:
            maxlen = timelist[i].length
            maxindex = i
    # 遍历其他课程，判断结束时间是否和最长的课程相同
    for i in range(len(timelist)):
        if i == maxindex:
            continue   # 跳过最长的课程
        if timelist[i].start + timelist[i].length != timelist[maxindex].start + maxlen:
            if isrequired:
                return False  # 发现起始时间不同，返回False
            else:
                totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的结束时间都和最长的课程相同，返回True


# 给定的课程必须在一天中的不同时间教授
# 对于给定的任意一个课程，其起始时间和结束时间和其他课程的起始和结束时间都不能有任何重叠。
def DifferentTime(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，判断是否有课程的时间重叠
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if timelist[i].start < timelist[j].start < timelist[i].start + timelist[i].length or \
               timelist[i].start < timelist[j].start + timelist[j].length < timelist[i].start + timelist[i].length or \
               timelist[j].start < timelist[i].start < timelist[j].start + timelist[j].length or \
               timelist[j].start < timelist[i].start + timelist[i].length < timelist[j].start + timelist[j].length:
                if isrequired:
                    return False  # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的时间都不重叠，返回True

# 给定的课程必须在同一天授课，无论其开始时间段和周数如何。如果课程在一周中的不同日期上课，则会议较少的课程必须在会议较多的课程使用的日期的子集上课
# 对于约束中的任意两个课程中的days变量，在位串之间执行二进制 “or” 操作，如果结果为1，则说明这两个课程在同一天上课。不考虑weeks变量。
def SameDays(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，计算每一对课程的days变量的“or”结果
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if bin(int(timelist[i].days, 2) | int(timelist[j].days, 2)).count('1') == 1:
                if isrequired:
                    return False  # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的日期都相同，返回True

# 给定的课程必须在不同日子教授，无论其开始时间段和周数如何
# 对于约束中的任意两个课程中的days变量，在位串之间执行二进制 “and” 操作，如果结果为0，则说明这两个课程在不同日期上课。不考虑weeks变量。
def DifferentDays(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，计算每一对课程的days变量的“and”结果
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if bin(int(timelist[i].days, 2) & int(timelist[j].days, 2)).count('1') == 0:
                if isrequired:
                    return False  # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的日期都不同，返回True

# 给定的课程必须在同一周上课，无论其开始时间段如何。如果课程在一天中的不同时间段上课，则会议较少的课程必须在会议较多的课程使用的时间段的子集上课
# 对于约束中的任意两个课程中的weeks变量，在位串之间执行二进制 “or” 操作，如果结果为1，则说明这两个课程在同一周上课。不考虑days变量。
def SameWeeks(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，计算每一对课程的weeks变量的“or”结果
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if bin(int(timelist[i].weeks, 2) | int(timelist[j].weeks, 2)).count('1') == 1:
                if isrequired:
                    return False  # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的周次都相同，返回True

# 给定的课程必须在不同周上课，无论其开始时间段如何。
# 对于约束中的任意两个课程中的weeks变量，在位串之间执行二进制 “and” 操作，如果结果为0，则说明这两个课程在不同周上课。不考虑days变量。
def DifferentWeeks(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，计算每一对课程的weeks变量的“and”结果
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if bin(int(timelist[i].weeks, 2) & int(timelist[j].weeks, 2)).count('1') == 0:
                if isrequired:
                    return False  # 发现起始时间不同，返回False
                else:
                    totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的周次都不同，返回True

# 对官方文档中的公式有疑问
# 此约束中的任意两个课程在时间上不能有任何重叠。
# 对于约束中的任意两个课程，首先计算其开始时间和结束时间是否有重叠，然后对其weeks和days的位串之间执行二进制 “or” 操作，如果结果为0，则说明这两个课程没有时间上的重叠。
def NotOverlap(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，判断是否有课程的时间重叠
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if timelist[i].start < timelist[j].start < timelist[i].start + timelist[i].length or \
               timelist[i].start < timelist[j].start + timelist[j].length < timelist[i].start + timelist[i].length or \
               timelist[j].start < timelist[i].start < timelist[j].start + timelist[j].length or \
               timelist[j].start < timelist[i].start + timelist[i].length < timelist[j].start + timelist[j].length:
                # 发现时间重叠，计算weeks和days的位串之间执行“and”操作
                if bin(int(timelist[i].weeks, 2) & int(timelist[j].weeks, 2)).count('1') == 1 and \
                   bin(int(timelist[i].days, 2) & int(timelist[j].days, 2)).count('1') == 1:
                    if isrequired:
                        return False  # 发现起始时间不同，返回False
                    else:
                        totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的时间都不重叠，返回False


# 此约束中的任意两个课程在时间上必须有一些重叠。
# 对于约束中的任意两个课程，首先计算其开始时间和结束时间是否有重叠，然后对其weeks和days的位串之间执行二进制 “and” 操作，如果结果为1，则说明这两个课程有时间上的重叠。
def Overlap(classlist, isrequired, penalty):
    totalpenalty = 0
    # 先获取课程时间列表
    timelist = GetClassTime(classlist)
    # 遍历课程时间列表，判断是否有课程的时间重叠
    for i in range(len(timelist)):
        for j in range(i+1, len(timelist)):
            if timelist[i].start < timelist[j].start < timelist[i].start + timelist[i].length or \
               timelist[i].start < timelist[j].start + timelist[j].length < timelist[i].start + timelist[i].length or \
               timelist[j].start < timelist[i].start < timelist[j].start + timelist[j].length or \
               timelist[j].start < timelist[i].start + timelist[i].length < timelist[j].start + timelist[j].length:
                # 发现时间重叠，计算weeks和days的位串之间执行“or”操作
                if bin(int(timelist[i].weeks, 2) | int(timelist[j].weeks, 2)).count('1') == 0 and \
                   bin(int(timelist[i].days, 2) | int(timelist[j].days, 2)).count('1') == 0:
                    if isrequired:
                        return False  # 发现起始时间不同，返回False
                    else:
                        totalpenalty += penalty*len(classlist)  # 计算违反约束的惩罚值
    return True if isrequired else totalpenalty   # 所有课程的时间都不重叠，返回True



